﻿#region Imports
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using CommonUtilities.Attributes;
using CommonUtilities.Exceptions;
using CommonUtilities.Extensions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
#endregion

namespace CommonUtilities.UnitTest.UTExtensions
{
    [System.ComponentModel.Description("Description_test")]
    [DisplayName("DisplayName_test")]
    public class Test_Entity
    {
        #region Declarations
        private long byteSize;
        #endregion

        #region Properties
        [Unique]
        [Sortable]
        [DisplayName("Dir. name")]
        public string DirName { get; private set; }
        [Browsable(false)]
        [DisplayName("Full path")]
        public string DirPath { get; private set; }
        [Browsable(false)]
        [DisplayName("Relative path")]
        public string RelPath { get; private set; }
        [Sortable]
        [DisplayName("Size (bytes)")]
        public long ByteSize { get { return byteSize; } private set { byteSize = value; } }
        [DisplayName("Directory size")]
        public string DirSize { get { return ByteSize.ToPracticalSize(); } }
        [Browsable(false)]
        public long Sort { get; set; }
        [Sortable]
        [DisplayName("# files")]
        public long FileCount { get; private set; }
        [Browsable(false)]
        [DisplayName("Avg. size (bytes)")]
        public long AvgByteSize { get { return FileCount == 0 ? 0 : ByteSize / FileCount; } }
        [DisplayName("Avg. size per file (B)")]
        public string AvgFileSize { get { return AvgByteSize.ToPracticalSize(); } }
        [DisplayName("Last modified")]
        public string LastModified { get; private set; }
        #endregion

        #region Constructors
        public Test_Entity()
        {
            ;
        }

        public Test_Entity(string dirPath, long size, long sort, long fileCount)
        {
            Sort = sort;
            DirName = DirPath = RelPath = dirPath;
            FileCount = fileCount;
            ByteSize = size;
            LastModified = "--";
        }

        public Test_Entity(string dirPath, int sort)
        {
            PopulateProperties(new DirectoryInfo(dirPath), sort);
        }

        public Test_Entity(DirectoryInfo dirInfo, int sort)
        {
            PopulateProperties(dirInfo, sort);
        }
        #endregion

        #region Methods
        private void PopulateProperties(DirectoryInfo dirInfo, int sort)
        {
            Sort = sort;
            DirName = dirInfo.Name;
            DirPath = dirInfo.FullName;
            RelPath = ".." + Path.DirectorySeparatorChar
                + dirInfo.FullName.Replace(dirInfo.Parent.FullName, "").Trim(Path.DirectorySeparatorChar);
            FileCount = CalculateDirectorySize(DirPath, out byteSize);
            LastModified = dirInfo.LastWriteTime.ToString("yyyy-MMM-dd HH:mm:ss");
        }

        public static long CalculateDirectorySize(string sDirectoryPath, out long dirSize)
        {
            ObservableCollection<string> allFilePaths = new ObservableCollection<string>();
            allFilePaths = new ObservableCollection<string>(Directory.GetFiles(sDirectoryPath, "*.*", SearchOption.AllDirectories));

            if (allFilePaths.LongCount() > 0)
            {
                ObservableCollection<FileInfo> allFiles = new ObservableCollection<FileInfo>();
                allFilePaths.ToList().ForEach(file => allFiles.Add(new FileInfo(file)));

                dirSize = allFiles.Select(file => file.GetLength()).Sum();
            }
            else
                dirSize = 0;

            return allFilePaths.LongCount();
        }
        #endregion
    }

    [TestClass]
    public class AttributeExtensions_Test
    {
        #region Test methods
        [TestMethod]
        public void GetClassDescription_Test()
        {
            // EXPECTED
            string expected = "Description_test";

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            string actual = ent.GetClassDescription();
            string regression = this.GetClassDescription();

            // RESULT
            Assert.AreEqual(expected, actual, UnitTestFailedException.Description);
            Assert.AreNotEqual(regression, actual, UnitTestFailedException.Description);
        }

        [TestMethod]
        public void GetAttributeProperties_Test()
        {
            // EXPECTED
            ObservableCollection<string> expected = new ObservableCollection<string>();
            expected.Add("Description");
            expected.Add("TypeId");

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            ObservableCollection<string> actual = new ObservableCollection<string>();
            ent.GetAttributeProperties<Test_Entity, System.ComponentModel.DescriptionAttribute>().ForEach(prop => actual.Add(prop.Name));

            // RESULT
            if (actual.GetCount() != expected.GetCount()) throw new UnitTestFailedException();
            for (int i = 0; i < expected.GetCount(); i++)
            {
                Assert.AreEqual(expected.OrderByDirection(val => val.ToString()).ElementAt(i).ToString(),
                   actual.OrderByDirection(val => val.ToString()).ElementAt(i).ToString(),
                   UnitTestFailedException.Description);
            }
        }

        [TestMethod]
        public void GetAttributeProperty_Test()
        {
            // EXPECTED
            string expected = "Description_test";

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            Attribute attr = ent.GetAttribute<Test_Entity, System.ComponentModel.DescriptionAttribute>();
            PropertyInfo prop1 = ent.GetAttributeProperty<Test_Entity, System.ComponentModel.DescriptionAttribute>("Description");
            PropertyInfo prop2 = ent.GetAttributeProperty<Test_Entity, System.ComponentModel.DescriptionAttribute>("_Description");
            string actual = (string)prop1.GetValue(attr);
            string regression = (string)prop2.GetValue(attr);

            // RESULT
            Assert.AreEqual(expected, actual, UnitTestFailedException.Description);
            Assert.AreNotEqual(expected, regression, UnitTestFailedException.Description);
        }

        [TestMethod]
        public void GetAttributes_Test()
        {
            // EXPECTED
            ObservableCollection<Attribute> expected = new ObservableCollection<Attribute>();
            expected.Add(new System.ComponentModel.DescriptionAttribute("Description_test"));
            expected.Add(new DisplayNameAttribute("DisplayName_test"));

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            ObservableCollection<Attribute> actual = new ObservableCollection<Attribute>();
            IEnumerator e = ent.GetAttributes().GetEnumerator();
            e.Reset();
            while (e.MoveNext()) actual.Add((Attribute)e.Current);

            // RESULT
            if (actual.GetCount() != expected.GetCount()) throw new UnitTestFailedException();
            for (int i = 0; i < expected.GetCount(); i++)
            {
                Assert.AreEqual(expected.OrderByDirection(val => val.ToString()).ElementAt(i).ToString(),
                   actual.OrderByDirection(val => val.ToString()).ElementAt(i).ToString(),
                   UnitTestFailedException.Description);
            }
        }

        [TestMethod]
        public void GetAttribute_Test()
        {
            // EXPECTED
            Attribute expected = new DisplayNameAttribute("DisplayName");

            // ACTUAL
            Attribute actual = this.GetAttribute<AttributeExtensions_Test, DisplayNameAttribute>();
            Attribute regression = this.GetAttribute<AttributeExtensions_Test, TestClassAttribute>();

            Assert.AreEqual(expected.ToString(), actual.ToString(), UnitTestFailedException.Description);
            Assert.AreNotEqual(regression.ToString(), actual.ToString(), UnitTestFailedException.Description);
        }

        [TestMethod]
        public void GetPropertyAttributes_Test()
        {
            // EXPECTED
            ObservableCollection<Attribute> expected = new ObservableCollection<Attribute>();
            expected.Add(new DisplayNameAttribute("Full path"));
            expected.Add(new UniqueAttribute());
            expected.Add(new SortableAttribute());

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            string propName = "DirName";
            ObservableCollection<Attribute> actual = ent.GetPropertyAttributes<Test_Entity>(propName).ToObservableCollection();
            ObservableCollection<Attribute> regression = new ObservableCollection<Attribute>();
            regression.Add(new System.ComponentModel.DescriptionAttribute("Description_test_1"));
            regression.Add(new System.ComponentModel.DescriptionAttribute("Description_test_2"));
            regression.Add(new System.ComponentModel.DescriptionAttribute("Description_test_3"));

            // RESULT
            if (actual.GetCount() != expected.GetCount()) throw new UnitTestFailedException();
            for (int i = 0; i < expected.GetCount(); i++)
            {
                Assert.AreEqual(expected.OrderByDirection(val => val.ToString()).ElementAt(i).TypeId,
                   actual.OrderByDirection(val => val.ToString()).ElementAt(i).TypeId,
                   UnitTestFailedException.Description);
            }

            if (regression.GetCount() != expected.GetCount()) throw new UnitTestFailedException();
            for (int i = 0; i < expected.GetCount(); i++)
            {
                Assert.AreNotEqual(expected.OrderByDirection(val => val.ToString()).ElementAt(i).TypeId,
                   regression.OrderByDirection(val => val.ToString()).ElementAt(i).TypeId,
                   UnitTestFailedException.Description);
            }
        }

        [TestMethod]
        public void GetPropertyAttribute_Test_DefaultCtorAttrib()
        {
            // EXPECTED
            DisplayNameAttribute expected = new DisplayNameAttribute("Dir. name");

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            string propName = "DirName";
            DisplayNameAttribute actual1 = ent.GetPropertyAttribute<Test_Entity, DisplayNameAttribute>(propName); // Correct class, correct property
            //DisplayNameAttribute regression1 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity, TestClassAttribute>(propName); // Incorrect class, correct property

            DisplayNameAttribute actual2 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "DisplayName"); // Correct class, correct property
            DisplayNameAttribute regression2 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "_DisplayName"); // Correct class, incorrect property
            DisplayNameAttribute regression3 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "TypeId"); // Incorrect class, correct property
            DisplayNameAttribute regression4 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "_DisplayName"); // Incorrect class, incorrect property

            // RESULT
            Assert.AreEqual(expected.DisplayName, actual1.DisplayName, UnitTestFailedException.Description);
            //Assert.AreNotEqual(expected.DisplayName, regression1.Coalesce("TypeId"), UnitTestFailedException.Description);

            Assert.AreEqual(expected.DisplayName, actual2.DisplayName, UnitTestFailedException.Description);
            Assert.AreNotEqual(expected.TypeId, regression2.Coalesce<DisplayNameAttribute, object>("TypeId"), UnitTestFailedException.Description);
            Assert.AreNotEqual(expected.TypeId, regression3.Coalesce<DisplayNameAttribute, object>("TypeId"), UnitTestFailedException.Description);
            Assert.AreNotEqual(expected.TypeId, regression4.Coalesce<DisplayNameAttribute, object>("TypeId"), UnitTestFailedException.Description);
        }

        [TestMethod]
        public void GetPropertyAttribute_Test_CustCtorAttrib()
        {
            // EXPECTED
            DisplayNameAttribute expected1 = new DisplayNameAttribute("Full path");
            BrowsableAttribute expected2 = new BrowsableAttribute(false);

            // ACTUAL
            Test_Entity ent = new Test_Entity();
            string propName = "DirPath";
            DisplayNameAttribute actual1 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity, DisplayNameAttribute>(propName); // Correct class, correct property
            //DisplayNameAttribute regression1 = (DisplayNameAttribute)ent.GetPropertyAttribute<Test_Entity, TestClassAttribute>(propName); // Incorrect class, correct property

            // Use for non-abstract type where public parameter-less constructor is not available
            BrowsableAttribute actual2 = (BrowsableAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "BrowsableAttribute"); // Correct class, correct property
            BrowsableAttribute regression2 = (BrowsableAttribute)ent.GetPropertyAttribute<Test_Entity>(propName, "BrowsableAttribute"); // Correct class, incorrect property

            // RESULT
            Assert.AreEqual(expected1.DisplayName, actual1.DisplayName, UnitTestFailedException.Description);
            //Assert.AreNotEqual(expected1.DisplayName, regression1.Coalesce("TypeId"), UnitTestFailedException.Description);

            Assert.AreEqual(expected2.Browsable, actual2.Browsable, UnitTestFailedException.Description);
            Assert.AreEqual(expected2.Browsable, regression2.Browsable, UnitTestFailedException.Description);
            Assert.AreEqual(expected2.TypeId, regression2.Coalesce<BrowsableAttribute, object>("TypeId"), UnitTestFailedException.Description);
        }
        #endregion      
    }
}
